package com.ylr.admin.framework.service.impl;

import com.ylr.admin.common.enums.UriEnum;
import com.ylr.admin.framework.entity.UriEntity;
import com.ylr.admin.framework.service.RoleService;
import com.ylr.admin.framework.service.RoleUriService;
import com.ylr.admin.framework.service.RoleUserService;
import com.ylr.admin.framework.service.UserAuthService;
import com.ylr.base.common.constant.RedisConstant;
import com.ylr.base.common.exception.YlrException;
import com.ylr.base.common.po.user.UserAuthenticationDto;
import com.ylr.base.common.utils.RandomNumUtils;
import com.ylr.base.common.utils.ThrottleUtils;
import com.ylr.base.framework.service.AsyncService;
import com.ylr.redis.framework.service.RedisService;
import com.ylr.redis.framework.service.RedisStringService;
import com.ylr.redis.framework.service.RedissonService;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-05-11 17:38:21
 * className: UserAuthServiceImpl
 * version: 1.0
 * description:
 */
@Service("userAuthService")
@AllArgsConstructor
public class UserAuthServiceImpl implements UserAuthService {

    private static final Logger log = LoggerFactory.getLogger(UserAuthServiceImpl.class);

    private final RoleService roleService;

    private final RoleUserService roleUserService;

    private final RoleUriService roleUriService;

    private final AsyncService asyncService;

    private final RedisStringService redisStringService;

    private final RedisService redisService;

    private final RedissonService redissonService;

    private final ThrottleUtils throttleUtils;

    @Override
    public UserAuthenticationDto getAuthentication(Long userId) {

        String redisKey = RedisConstant.USER_AUTHENTICATION_PREFIX + userId;
        // 1、查询缓存
        UserAuthenticationDto userAuthentication = redisStringService.get(redisKey);
        if (Objects.nonNull(userAuthentication)) {
            // 尝试更新缓存过期时间
            redisService.tryUpdateKeyExpire(redisKey, 1800, RandomNumUtils.randomNum(3600, 10));
            return userAuthentication;
        }

        String lockKey = RedisConstant.LOCK_PREFIX + redisKey;
        Supplier<UserAuthenticationDto> supplier = () -> {
            // double check
            UserAuthenticationDto userAuthenticationDto = redisStringService.get(redisKey);
            if (Objects.nonNull(userAuthenticationDto)) {
                return userAuthenticationDto;
            }

            // 2、查询数据库
            userAuthenticationDto = new UserAuthenticationDto();
            userAuthenticationDto.setModuleUriSetMap(new HashMap<>())
                    .setModuleUriListMap(new HashMap<>())
                    .setPermitSet(new HashSet<>())
                    .setRoleSet(new HashSet<>());

            // 查询用户关联的角色主键集合
            Set<Long> roleIdSet = roleUserService.getRoleIdList(userId);
            if (roleIdSet.isEmpty()) {
                log.info("用户关联的角色为空");

                // 添加缓存
                redisStringService.set(redisKey, userAuthenticationDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return userAuthenticationDto;
            }

            CompletableFuture<Set<String>> future = asyncService.supplyAsync(() -> {
                /*
                    异步操作
                    查询角色键集合
                 */
                return roleService.getRoleKeySet(roleIdSet);
            });

            // 查询所有所有关联的系统接口，并从中获取接口路径
            List<UriEntity> uriList = roleUriService.getSimpleUriList(roleIdSet);
            if (uriList.isEmpty()) {
                log.info("根据用户已关联角色，匹配到的系统接口为空");
            }

            Map<Integer, Set<String>> moduleUriSetMap = userAuthenticationDto.getModuleUriSetMap();
            Map<Integer, List<String>> moduleUriListMap = userAuthenticationDto.getModuleUriListMap();
            Set<String> permitSet = userAuthenticationDto.getPermitSet();

            for (UriEntity uri : uriList) {
                if (UriEnum.LIKE_MATCH.getValue().equals(uri.getCheckMode())) {
                    List<String> _uriList = moduleUriListMap.computeIfAbsent(uri.getCheckMode(), k -> new ArrayList<>());
                    // 模糊匹配
                    _uriList.add(uri.getUri());
                } else {
                    Set<String> uriSet = moduleUriSetMap.computeIfAbsent(uri.getModule(), k -> new HashSet<>());
                    // 全值匹配
                    uriSet.add(uri.getUri());
                }
                permitSet.add(uri.getPermit());
            }
            Set<String> roleSet;
            try {
                roleSet = future.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("异步查询角色键集合失败。message={}", e.getMessage(), e);
                throw new YlrException();
            }
            userAuthenticationDto.setRoleSet(roleSet);

            // 3、添加缓存
            redisStringService.set(redisKey, userAuthenticationDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

            return userAuthenticationDto;
        };
        // 加锁运行
        return redissonService.lockRun(lockKey, supplier);
    }
}
